﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using Framework.Model;
using System.Linq;
using CharacterModel.Extensions;

namespace CharacterModel
{
    public class FaceModel
    {
        private readonly IEnumerable<string> _featureIds;

        private Rect? _featureBounds;
        public Rect FeatureBounds
        {
            get
            {
                if (_featureBounds == null) _featureBounds = GetFeatureBounds();
                return _featureBounds.Value;
            }
            private set { _featureBounds = value; }
        }

        public FaceModel(string faceIdentifier)
        {
            _featureIds = faceIdentifier
                .Split('_')
                .ToList();
        }

        private readonly Dictionary<LayerTypeEnum, Color> _colorMap = new Dictionary<LayerTypeEnum, Color>();

        private IEnumerable<Feature> _features;
        public IEnumerable<Feature> Features
        {
            get
            {
                if (_features == null)
                {
                    var featureList = new List<Feature>();
                    foreach (var featureId in _featureIds)
                    {
                        var featureIdParts = featureId.Split('|');
                        var feature = Repository.Get<Feature>(Convert.ToInt32(featureIdParts[0]));
                        if (feature == null) continue;
                        featureList.Add(feature);
                        if (featureIdParts.Length < 2) continue;

                        LayerTypeEnum layerTypeEnum;
                        if (feature.FeatureType.FeatureTypeEnum == FeatureTypeEnum.Head)
                        {
                            SetToColorMap(featureIdParts.ElementAtOrDefault(1), LayerTypeEnum.FaceColor);
                            SetToColorMap(featureIdParts.ElementAtOrDefault(2), LayerTypeEnum.HairShapeAboveColor);
                            SetToColorMap(featureIdParts.ElementAtOrDefault(2), LayerTypeEnum.HairShapeBelowColor);
                        }
                        else if (feature.FeatureType.FeatureTypeEnum == FeatureTypeEnum.Eyes)
                            SetToColorMap(featureIdParts.ElementAt(1), LayerTypeEnum.EyesColor);
                        else if (feature.FeatureType.FeatureTypeEnum == FeatureTypeEnum.Mouth)
                            SetToColorMap(featureIdParts.ElementAt(1), LayerTypeEnum.MouthLipColor);
                        else continue;
                    }
                    _features = featureList;
                    FeatureBounds = GetFeatureBounds();
                }
                return _features;
            }
        }

        private void SetToColorMap(string colorString, LayerTypeEnum layerTypeEnum)
        {
            if (colorString == null) return;

            var color = Color.FromRgb(
                byte.Parse(colorString.Substring(0, 2), NumberStyles.HexNumber),
                byte.Parse(colorString.Substring(2, 2), NumberStyles.HexNumber),
                byte.Parse(colorString.Substring(4, 2), NumberStyles.HexNumber));
            _colorMap[layerTypeEnum] = color;
        }

        private Rect GetFeatureBounds()
        {
            return Features
                .SelectMany(feature => new[] {feature.Bounds.TopLeft, feature.Bounds.BottomRight})
                .BoundingRectangle();
        }

        private static Rect GetMarkerBounds(Feature feature, params MarkerPointTypeEnum[] markerPointTypeEnums)
        {
            return feature
                .MarkerPoints
                .Where(markerPoint => markerPointTypeEnums.Contains(markerPoint.MarkerPointType.MarkerPointTypeEnum))
                .Select(markerPoint => new Point(markerPoint.PointX, markerPoint.PointY))
                .BoundingRectangle();
        }

        public IEnumerable<ColorRegion> GetColorRegions()
        {
            var transformGeometry = Features.Count() > 1;
            var featureTransform = new Dictionary<FeatureTypeEnum, Transform>();
            if (transformGeometry)
            {
                featureTransform[FeatureTypeEnum.Eyes] = GetFeatureTransform(Features, FeatureTypeEnum.Eyes, MarkerPointTypeEnum.EyeBoundary1, MarkerPointTypeEnum. EyeBoundary3, MarkerPointTypeEnum.EyeBoundary2, MarkerPointTypeEnum.EyeBoundary4);
                featureTransform[FeatureTypeEnum.Nose] = GetFeatureTransform(Features, FeatureTypeEnum.Nose, MarkerPointTypeEnum.NoseBoundary3, MarkerPointTypeEnum.NoseBoundary4, MarkerPointTypeEnum.NoseBoundary1, MarkerPointTypeEnum.NoseBoundary2);
                featureTransform[FeatureTypeEnum.Mouth] = GetFeatureTransform(Features, FeatureTypeEnum.Mouth, MarkerPointTypeEnum.MouthBoundary3, MarkerPointTypeEnum.MouthBoundary4, MarkerPointTypeEnum.MouthBoundary2, MarkerPointTypeEnum.MouthBoundary1);
                featureTransform[FeatureTypeEnum.Head] = Transform.Identity;
            }

            var colorRegions = Features
                .SelectMany(feature => feature
                                           .Layers
                                           .SelectMany(
                                               layer =>
                                               layer
                                                   .Paths
                                                   .Where(path => !string.IsNullOrEmpty(path.SvgPath))
                                                   .Select(path => new {Region = layer.LayerType.GetPathAsColorRegion(path, _colorMap.ContainsKey(layer.LayerType.LayerTypeEnum)
                                                       ? (Color?)_colorMap[layer.LayerType.LayerTypeEnum] : null),
                                                                           Feature = feature
                                                                       })))
                .ToList();

            if (transformGeometry)
            {
                foreach (var a in colorRegions)
                {
                    a.Region.Geometry = PathGeometry.CreateFromGeometry(Geometry.Parse(a.Region.Path));
                    a.Region.Geometry.Transform = featureTransform[a.Feature.FeatureType.FeatureTypeEnum];
                }
            }

            return colorRegions.Select(a => a.Region).ToList();
        }

        private static Transform GetFeatureTransform(IEnumerable<Feature> featuresToRender, FeatureTypeEnum otherFeatureType, params MarkerPointTypeEnum[] markerPointTypeEnums)
        {
            var headFeature = featuresToRender.FirstOrDefault(f => f.FeatureType.FeatureTypeEnum == FeatureTypeEnum.Head);
            var otherFeature = featuresToRender.FirstOrDefault(f => f.FeatureType.FeatureTypeEnum == otherFeatureType);
            if (headFeature != null && headFeature.MarkerPoints.Any() && otherFeature != null && otherFeature.MarkerPoints.Any())
            {
                var headRectangle = GetMarkerBounds(headFeature, markerPointTypeEnums);
                var otherRectangle = GetMarkerBounds(otherFeature, markerPointTypeEnums);

                var transformGroup = new TransformGroup();
                transformGroup.Children.Add(new TranslateTransform(-otherRectangle.Left, -otherRectangle.Top));
                transformGroup.Children.Add(new ScaleTransform(headRectangle.Width/otherRectangle.Width, headRectangle.Height/otherRectangle.Height));
                transformGroup.Children.Add(new TranslateTransform(headRectangle.Left, headRectangle.Top));
                return transformGroup;
            }
            return Transform.Identity;
        }

        public DrawingVisual GetDrawingVisual(int width, int height, int padding)
        {
            return Utility.GetScaledDrawingVisual(dc =>
                                     {
                                         foreach (var colorRegion in GetColorRegions())
                                             dc.DrawGeometry(new SolidColorBrush(colorRegion.Color), null, Geometry.Parse(colorRegion.Path));
                                     }, width, height, padding);
        }

        public Repository Repository { get; set; }
    }
}